﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Collections.Generic;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using System.Runtime.Serialization;
using System.Diagnostics;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// Similar to a thread-safe list, but supports value types (i.e. list of longs, ints, etc)
    /// </summary>	
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
    [Browsable(true)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeListValues<TListType> : Lockable, INotifyCollectionChanged,
        IList<TListType>, ISupportsAdd<TListType>, ISupportsClear, ISupportsCount, ISupportsRemove<TListType>
    {
        public override string ToString()
        {
            return _count.ToString();
        }
        [NonSerialized]
        protected List<TListType> _listCached;

        [DataMember]
        public List<TListType> _list = new List<TListType>();

        [DataMember]
        public int _count;

        [NonSerialized]
        protected bool _changed;

        /// <summary>
        /// Returns a somewhat-accurate count of items in the list
        /// </summary>        
        public int Count { get { return _count; } }

        /// <summary>
        /// Adds multiple items to the list
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public int Add(IEnumerable<TListType> items)
        {
            List<TListType> itemsAdded = null;

            if (CollectionChanged != null)
                itemsAdded = new List<TListType>();

            int found = 0;

            AquireLock();
            {
                foreach (TListType item in items)
                {
                    if (!_list.Contains(item))
                    {
                        if (itemsAdded != null) itemsAdded.Add(item);
                        _list.Add(item);
                        _count++;
                        _changed = true;
                        found++;
                    }
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
            {
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, itemsAdded);
            }
            return found;
        }

        /// <summary>
        /// Adds an item to the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Add(TListType item)
        {
            bool found = true;

            AquireLock();
            {
                if (!_list.Contains(item))
                {
                    _list.Add(item);
                    _count++;
                    _changed = true;
                    found = false;
                }
            }
            ReleaseLock();

            if (!found)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return !found;
        }

        /// <summary>
        /// determines if item is in the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsInList(TListType item)
        {
            bool found;

            AquireLock();
            {
                found = _list.Contains(item);
            }
            ReleaseLock();

            return found;
        }

        public TListType[] ArrayOfItems { get { return AllItems.ToArray(); } }

        /// <summary>
        /// Returns a (possibly cached) list containing all list items
        /// </summary>
        public List<TListType> AllItems
        {
            get
            {
                List<TListType> list;
                AquireLock();
                {
                    if (_changed || _listCached == null)
                    {
                        list = new List<TListType>(_list);
                        Interlocked.Exchange<List<TListType>>(ref _listCached, list);
                        _changed = false;
                    }
                    else
                    {
                        list = _listCached;
                    }
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Removes a single item (the first item) where match(item) == true
        /// Returns true if any items were removed
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public bool Remove(Func<TListType, bool> match)
        {
            bool found = false;
            List<TListType> items = null;
            if (CollectionChanged != null)
                items = new List<TListType>();

            AquireLock();
            {
                for (int n = 0; n < _list.Count; n++)
                {
                    TListType item = _list[n];
                    if (match(item))
                    {
                        if (items != null) items.Add(item);
                        _list.RemoveAt(n);
                        _count--;
                        _changed = true;
                        found = true;
                        break;
                    }
                }
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, items);
            return found;
        }

        /// <summary>
        /// Removes item from the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(TListType item)
        {
            bool found = false;

            AquireLock();
            {
                if (_list.Remove(item))
                {
                    _count--;
                    _changed = true;
                    found = true;
                }
            }
            ReleaseLock();

            if (found)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
            return found;
        }

        /// <summary>
        /// Removes all items where match(item) == true
        /// Returns the number of removed items
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public int RemoveMultiple(Func<TListType, bool> match)
        {
            List<TListType> items = null;
            if (CollectionChanged != null)
            {
                items = new List<TListType>();
            }

            int removed = 0;

            AquireLock();
            {
                for (int n = _list.Count - 1; n >= 0; n--)
                {
                    TListType item = _list[n];
                    if (match(item))
                    {
                        _list.RemoveAt(n);
                        if (items != null)
                        {
                            items.Add(item);
                        }
                        _count--;
                        _changed = true;
                        removed++;
                    }
                }
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, items);
            return removed;
        }

        /// <summary>
        /// Clears the list
        /// </summary>        
        public void Clear()
        {
            IList<TListType> items = null;

            if (CollectionChanged != null)
                items = new List<TListType>(AllItems);

            AquireLock();
            {
                _list.Clear();
                _count = 0;
                _changed = true;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        /// <summary>
        /// Finds an item in the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public TListType Find(TListType item)
        {
            int index;

            AquireLock();
            {
                index = _list.IndexOf(item);
            }
            ReleaseLock();

            return (index >= 0) ? item : default(TListType);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TListType> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TListType changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TListType>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region IList<TListType> Members

        public bool IsReadOnly
        {
            get { return false; }
        }

        public int IndexOf(TListType item)
        {
            int idx;

            AquireLock();
            {
                idx = _list.IndexOf(item);
            }
            ReleaseLock();

            return idx;
        }

        public void Insert(int index, TListType item)
        {
            throw new NotImplementedException();
        }

        public void RemoveAt(int index)
        {
            // Very simple but incomplete implementation for now
            AquireLock();
            {
                try
                {
                    _list.RemoveAt(index);
                }
                catch
                {
                    Debug.WriteLine("Could not remove at index " + index.ToString());
                }
            }
            ReleaseLock();            
        }

        public TListType this[int n]
        {
            get { return _list[n]; }
            set { _list[n] = value; }
        }

        #endregion

        #region ICollection<TListType> Members

        void ICollection<TListType>.Add(TListType item)
        {
            this.Add(item);
        }

        public void CopyTo(TListType[] array, int index)
        {
            AquireLock();
            {
                _list.CopyTo(array, index);
            }
            ReleaseLock();
        }

        public bool Contains(TListType item)
        {
            bool b;

            AquireLock();
            {
                b = _list.Contains(item);
            }
            ReleaseLock();

            return b;
        }

        public void CopyTo(TListType[] array)
        {
            AquireLock();
            {
                _list.CopyTo(array);
            }
            ReleaseLock();
        }

        #endregion

        #region IEnumerable<TListType> Members

        public IEnumerator<TListType> GetEnumerator()
        {
            return _list.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            int count = Count;
            for (int i = 0; i < count; ++i)
            {
                yield return _list[i];
            }
        }

        #endregion
    }
}